LLM 上下文压缩方案全面对比


业界主流方案、优势、实现方式与代码示例
更新日期:2026-06-17


背景问题

AI 对话中,每次请求都会把完整的历史消息(包括 thinking、工具调用输出、文件内容等)传给模型。随着对话拉长,上下文迅速膨胀,带来三个问题:

  • 成本高:按 token 计费,上下文越长越贵
  • 速度慢:首 token 延迟随上下文长度线性增加
  • 质量下降:”Lost in the Middle” 现象——模型对上下文中间部分关注力最差
1
Token 消耗 ≈ 系统提示 + 历史对话 × 轮数 + 工具输出 × 调用次数

即使模型支持 200K/1M token 窗口,装得下 ≠ 效果好 + 花钱少


方案一:删除压缩(Compaction)

原理

直接删除低信号内容,保留的原文一字不改。不生成新 token,零幻觉风险。

典型删除对象:

  • 工具调用的原始输出(grep 返回的 100 个文件 → 只保留匹配行)
  • 文件 diff 的全量内容 → 只保留变更摘要
  • 浏览器截屏的冗长 HTML → 只保留关键文本
  • 重复的 thinking 过程

优势

维度 评价
压缩比 50-70%
准确性 100%(保留下来的内容 = 原文)
幻觉风险 0%
速度 快(本地正则/规则即可,无需 LLM)

业界工具

  • Headroom — 开源上下文压缩层,10k+ star。SmartCrusher(JSON) + CodeCompressor(AST) + Kompress-base(文本),压缩率 90%+
  • RTK — 压缩 CLI 命令输出(git showls 等)
  • lean-ctx — CLI + MCP 工具的上下文精简

实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 简单的 compaction:只保留 grep 结果中的文件名和匹配行
function compactGrepOutput(raw: string): string {
const lines = raw.split('\n');
const significant = lines.filter(line => {
// 过滤掉:空行、只有行号的行、重复的分隔线
return line.trim() && !/^─+$/.test(line) && !/^\d+$/.test(line);
});
return significant.join('\n');
}

// 更智能的:JSON 响应只保留关键字段
function compactJsonResponse(raw: string, keepFields: string[]): string {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) {
return JSON.stringify(parsed.map(item => {
const result: Record<string, any> = {};
for (const field of keepFields) {
if (field in item) result[field] = item[field];
}
return result;
}));
}
return raw;
}

方案二:摘要压缩(Summarization)

原理

用 LLM 将多轮对话/长文本提炼成一段摘要,替换原始内容。后续对话基于摘要 + 最近的 N 轮完整消息进行。

优势

维度 评价
压缩比 极高(100 轮对话 → 200 字摘要)
信息密度 高(关键点被提炼出来)
适用场景 长周期对话、客服、教育辅导

劣势

维度 评价
幻觉风险 (摘要可能漏掉或歪曲细节)
额外开销 每次压缩需要一次 LLM 调用
反复压缩衰减 摘要的摘要会不断丢失信息

实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class ConversationSummarizer {
private summary = '';
private recentMessages: Message[] = [];
private readonly threshold = 4000; // token 阈值

addMessage(msg: Message) {
this.recentMessages.push(msg);
if (this.estimateTokens() > this.threshold) {
this.compress();
}
}

async compress() {
const toCompress = this.summary
+ '\n' + this.recentMessages.map(m => `${m.role}: ${m.content}`).join('\n');

const prompt = `请将以下对话历史压缩成一段简洁摘要,保留:
1. 用户的关键信息(身份、偏好、已做的决定)
2. 重要的任务状态和上下文
3. 后续对话可能需要的事实

原始对话:
${toCompress}

摘要:`;

this.summary = await llm.chat(prompt);
this.recentMessages = []; // 压缩后清空
// 或保留最后 1-2 轮防止断层
}

buildContext(): string {
if (!this.summary) return this.recentMessages.map(m => `${m.role}: ${m.content}`).join('\n');
return `【历史摘要】\n${this.summary}\n\n【最近对话】\n${
this.recentMessages.map(m => `${m.role}: ${m.content}`).join('\n')
}`;
}

private estimateTokens(): number {
const totalChars = this.summary.length
+ this.recentMessages.map(m => m.content.length).reduce((a, b) => a + b, 0);
return Math.ceil(totalChars / 2); // 中文字符估算
}
}

业界使用案例

产品 做法
OpenAI Codex CLI 调用 compact() API 对历史对话做摘要压缩
Claude Code 内置 compaction 机制,达到阈值后自动摘要
Anthropic Compaction API 服务端侧对话历史摘要(需付费)

方案三:滑动窗口(Sliding Window)

原理

只保留最近 N 轮对话,更早的全部丢弃。实现最简单。

常见策略

策略 做法
按轮数截断 保留最后 K 条消息
按 token 数截断 从尾部往前数,裁到预算内
按重要性排序截断 结合评分(相关性 + 时效性)丢弃最低分

优势

维度 评价
实现难度 ★☆☆☆☆ 极简单
速度 无额外开销
可预测 token 消耗固定

劣势

维度 评价
信息丢失 早期关键信息直接丢失
长任务中断 做了一半的任务,模型”失忆”

实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 按 token 数滑动窗口
function slidingWindow(messages: Message[], maxTokens: number): Message[] {
let total = 0;
const result: Message[] = [];

// 从最新的消息开始往前数
for (let i = messages.length - 1; i >= 0; i--) {
const tokens = estimateTokens(messages[i].content);
if (total + tokens > maxTokens) break;
total += tokens;
result.unshift(messages[i]); // 保持顺序
}

// 始终保留系统提示(第一条)
if (messages[0]?.role === 'system') {
if (result[0]?.role !== 'system') {
result.unshift(messages[0]);
}
}

return result;
}

// 更精细的:保留首尾,截断中间(Lost in the Middle 优化)
function smartWindow(messages: Message[], maxTokens: number): Message[] {
if (estimateTotalTokens(messages) <= maxTokens) return messages;

const systemMsg = messages.filter(m => m.role === 'system');
const history = messages.filter(m => m.role !== 'system');

// 保留最近 30% 的 token 预算给尾部
const tailBudget = Math.floor(maxTokens * 0.3);
const headBudget = maxTokens - tailBudget - estimateTotalTokens(systemMsg);

const tail: Message[] = [];
let tailTokens = 0;
for (let i = history.length - 1; i >= 0; i--) {
const t = estimateTokens(history[i].content);
if (tailTokens + t > tailBudget) break;
tailTokens += t;
tail.unshift(history[i]);
}

// 从头部取到 headBudget
const head: Message[] = [];
let headTokens = 0;
for (const msg of history) {
if (head.includes(msg) || tail.includes(msg)) break; // 不重复
const t = estimateTokens(msg.content);
if (headTokens + t > headBudget) break;
headTokens += t;
head.push(msg);
}

return [...systemMsg, ...head, ...tail];
}

方案四:向量记忆 / RAG(检索式记忆)

原理

不保存全部历史,而是把消息向量化存入数据库(Chroma、Milvus 等),每次只检索最相关的几条。

优势

维度 评价
理论上限 近乎无限记忆(只受存储限制)
相关性 高(语义检索找到最相关的)
Token 消耗 极低(只塞相关片段)

劣势

维度 评价
实现复杂度 ★★★★☆ 高
额外组件 需要向量数据库 + embedding 模型
检索质量 依赖 embedding 质量,可能漏检
延迟 每次多一次向量检索(通常 < 50ms)

实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class VectorMemory {
constructor(
private embeddingClient: EmbeddingAPI,
private vectorDb: VectorDatabase,
private topK = 5
) {}

async addMessage(msg: Message) {
const embedding = await this.embeddingClient.embed(msg.content);
await this.vectorDb.insert({
id: msg.id,
vector: embedding,
metadata: {
role: msg.role,
timestamp: msg.timestamp,
content: msg.content
}
});
}

async retrieve(query: string, limit = 5): Promise<Message[]> {
const queryEmbedding = await this.embeddingClient.embed(query);
const results = await this.vectorDb.search(queryEmbedding, limit);
return results.map(r => ({
role: r.metadata.role,
content: r.metadata.content,
timestamp: r.metadata.timestamp
}));
}

// 构建上下文:系统提示 + 检索结果 + 最近 N 轮
async buildContext(query: string, recentMessages: Message[]): Promise<string> {
const relevant = await this.retrieve(query, this.topK);
const context = [
'【相关历史】',
...relevant.map(m => `${m.role}: ${m.content}`),
'',
'【最近对话】',
...recentMessages.map(m => `${m.role}: ${m.content}`)
].join('\n');
return context;
}
}

方案五:多代理隔离(Multi-Agent Isolation)

原理

每个子代理维护自己的小上下文,只关注自己的任务。主代理只持有摘要级别的全局上下文。

优势

维度 评价
单 agent 上下文 小(只关注子任务)
隔离性好 一个 agent 的 thinking 不会污染另一个
可并行 多个 agent 可同时工作

劣势

维度 评价
架构复杂度 ★★★★★ 很高
协调开销 需要主 agent 做路由和汇总
信息孤岛 子 agent 看不到全局,可能做出次优决策

实现示意

1
2
3
4
5
6
7
8
9
10
11
                 ┌─────────────────┐
│ Orchestrator │ ← 只持有摘要上下文
│ (主代理) │
└────────┬────────┘

┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Frontend │ │ Backend │ │ Database │ ← 每个只关心自己的上下文
Agent │ │ Agent │ │ Agent
└────────────┘ └────────────┘ └────────────┘

方案六:上下文缓存(Context Caching)

原理

静态上下文(系统提示、文档、知识库)只在第一次完整传入,后续请求复用缓存结果,只传变化的部分。

提供商支持

平台 功能 效果
Kimi (月之暗面) 上下文缓存 API 降本 90%,首 token 延迟降 83%
OpenAI Prompt Caching 缓存命中时 50% 折扣
Anthropic Prompt Caching 缓存命中时降价 85-90%
Google Gemini Context Caching 长文档场景高效

适用场景

  • 固定文档的大量提问(产品说明书、法律文档、代码库分析)
  • 高流量的 AI 应用(同一份知识库被反复查询)
  • Agent 长期任务(系统提示不变,只变用户输入)

实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Pseudocode:上下文缓存 + 增量更新
class ContextCache {
private cache: Map<string, CachedContext> = new Map();

async getOrCreate(key: string, buildContext: () => Promise<string>): Promise<string> {
const cached = this.cache.get(key);
if (cached && !this.isExpired(cached)) {
return cached.context;
}

const context = await buildContext();
this.cache.set(key, {
context,
createdAt: Date.now(),
ttl: 10 * 60 * 1000 // 10 分钟
});
return context;
}

private isExpired(cached: CachedContext): boolean {
return Date.now() - cached.createdAt > cached.ttl;
}
}

方案七:Token 预算管理(Token Budgeting)

原理

把上下文窗口当成预算来分配,不同部分有不同的优先级。

常见分配

1
2
3
4
5
6
7
总窗口 100K tokens
├── 系统提示: 2K (2%) ← 固定
├── 对话历史: 10K (10%) ← 滑动窗口 + 摘要
├── 工具输出: 30K (30%)compaction 压缩
├── 检索文档: 30K (30%) ← 按相关性排序
├── 当前用户输入: 3K (3%) ← 完整保留
└── 模型回答预留: 25K (25%) ← 回复空间

实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class TokenBudget {
private readonly budget = {
system: 0.02, // 2%
history: 0.10, // 10%
tools: 0.30, // 30%
retrieval: 0.30, // 30%
input: 0.03, // 3%
output: 0.25 // 25%
};

constructor(private maxTokens: number) {}

allocate(): Record<string, number> {
return Object.fromEntries(
Object.entries(this.budget).map(([key, ratio]) => [
key, Math.floor(this.maxTokens * ratio)
])
);
}

enforce(section: string, content: string): string {
const limit = this.allocate()[section];
const tokens = estimateTokens(content);
if (tokens <= limit) return content;

// 超预算时截断到预算的 90%,留余量
const target = Math.floor(limit * 0.9);
return truncateToTokens(content, target);
}
}

方案八:Thinking 过滤(Thinking Stripping)

原理

在把上下文传给模型之前,剥离 assistant 回复中的 thinking/推理部分,只保留最终回答。

优势

维度 评价
信息损失 最小(最终回答已包含结论和关键推理)
压缩比 因模型而异(thinking 可占回复的 30-70%)
实现难度 ★☆☆☆☆ 简单

实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 过滤 thinking 内容(如 内容块)
function stripThinking(messages: Message[]): Message[] {
return messages.map(msg => {
if (msg.role !== 'assistant') return msg;

// 移除 块
const cleaned = msg.content.replace(/[\s\S]*?<\/thinking>/g, '');
return { ...msg, content: cleaned.trim() };
});
}

// 过滤工具调用过程中的中间输出
function stripToolOutput(messages: Message[]): Message[] {
return messages.filter(msg => {
// 保留最终的 tool result,但过滤中间的阶段性输出
if (msg.role === 'tool') {
return msg.isFinalResult ?? true; // 只保留标记为最终结果的
}
return true;
});
}

方案对比总结

方案 压缩比 信息损失 幻觉风险 实现难度 额外开销 适用场景
① 删除压缩 50-70% 0% ★☆☆☆☆ 工具输出、日志、grep结果
② 摘要压缩 90%+ ★★★☆☆ LLM调用 长对话、客服、辅导
③ 滑动窗口 可控 ★☆☆☆☆ 简单对话、短期任务
④ 向量记忆 极高 ★★★★☆ 向量检索 超长期对话、知识库问答
⑤ 多代理隔离 极高 ★★★★★ 协调开销 复杂工程、多步骤任务
⑥ 上下文缓存 依场景 0%(静态部分) 0% ★★☆☆☆ 固定文档高频查询
⑦ Token预算管理 可控 可控 ★★☆☆☆ 生产系统、成本控制
⑧ Thinking过滤 30-70% 极低 0% ★☆☆☆☆ AI Agent对话、编程助手

组合方案示例

实际生产环境不会只用一种方案,而是多层组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
接收用户输入


① Token 预算分配 ← 系统提示 2% + 回答预留 25%


② Thinking 过滤 ← 剥离上一轮 assistant 的


③ 删除压缩 (Compaction) ← 压缩工具输出、JSON 响应、日志


④ 滑动窗口 + 摘要 ← 最近 20 轮完整 + 更早的历史摘要


⑤ 向量检索 (RAG) ← 根据当前问题,检索最相关的历史


组合成最终上下文 → 传给 LLM

真实案例:OpenCode 当前可以做的是

1
2
3
4
5
6
7
8
9
10
当前状态(你正在用的):
✅ 系统提示 + Behavior Instructions(大几千 token)
❌ Thinking 不过滤(我的 全部进上下文)
❌ 工具输出不做压缩
❌ 对话不做摘要

可以立即做的优化:
1. 自定义 agent + prompt 规范输出格式
2. 创建 output-format skill 控制回答结构
3. 等待 OpenCode 支持 thinking 剥离(目前不支持)

参考资料


LLM 上下文压缩方案全面对比
https://neoisconstantine-github-io.pages.dev/2026/06/17/LLM 上下文压缩方案全面对比/
作者
constantine
发布于
2026年6月17日
许可协议